You Dont't Know JS 笔记(1)

Up & Going

Chapter 1: Into Programming

get user input

1
2
3
age = prompt( "Please tell me your age:" );

console.log( age );

Converting Between Types

1
2
3
4
5
var a = "42";
var b = Number( a );

console.log( a ); // "42"
console.log( b ); // 42

Scope

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function outer() {
var a = 1;

function inner() {
var b = 2;

// we can access both `a` and `b` here
console.log( a + b ); // 3
}

inner();

// we can only access `a` here
console.log( a ); // 1
}

outer();

So, code inside the inner() function has access to both variables a and b, but code in outer() has access only to a – it cannot access b because that variable is only inside inner().

Chapter 2: Into JavaScript

Values & Types

  • string
  • number
  • boolean
  • null and undefined
  • object
  • symbol (new to ES6)
1
2
a = null;
typeof a; // "object" -- weird, bug

Also, note a = undefined. We’re explicitly setting a to the undefined value, but that is behaviorally no different from a variable that has no value set yet, like with the var a; line at the top of the snippet. A variable can get to this “undefined” value state in several different ways, including functions that return no values and usage of the void operator.

array and function. But rather than being proper built-in types, these should be thought of more like subtypes – specialized versions of the object type.

强制类型转化

1
2
3
4
5
var a = "42";
var b = a * 1; // "42" implicitly coerced to 42 here

a; // "42"
b; // 42 -- the number!
1
2
3
4
5
var a = 42;
var b = a + 'a';

a; // 42
b; // "42a" -- the String!

You should take special note of the == and === comparison rules if you’re comparing two non-primitive values, like objects (including function and array). Because those values are actually held by reference, both == and === comparisons will simply check whether the references match, not anything about the underlying values.

For example, arrays are by default coerced to strings by simply joining all the values with commas (,) in between. You might think that two arrays with the same contents would be == equal, but they’re not:

1
2
3
4
5
6
7
var a = [1,2,3];
var b = [1,2,3];
var c = "1,2,3";

a == c; // true
b == c; // true
a == b; // false

how can all three of those comparisons be false? Because the b value is being coerced to the “invalid number value” NaN in the < and > comparisons, and the specification says that NaN is neither greater-than nor less-than any other value.

1
2
3
4
5
6
var a = 42;
var b = "foo";

a < b; // false
a > b; // false
a == b; // false

False

  • “” (empty string)
  • 0, -0, NaN (invalid number)
  • null, undefined
  • false

True

  • “hello”
  • 42
  • true
  • , [ 1, “2”, 3 ] (arrays)
  • { }, { a: 42 } (objects)
  • function foo() { .. } (functions)

Function Scopes

Hoisting

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var a = 2;

foo(); // works because `foo()`
// declaration is "hoisted"

function foo() {
a = 3;

console.log( a ); // 3

var a; // declaration is "hoisted"
// to the top of `foo()`
}

console.log( a ); // 2

Nested Scopes

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

function foo() {
var a = 1;

function bar() {
var b = 2;

function baz() {
var c = 3;

console.log( a, b, c ); // 1 2 3
}

baz();
console.log( a, b ); // 1 2
}

bar();
console.log( a ); // 1
}

foo();

Notice that c is not available inside of bar(), because it’s declared only inside the inner baz() scope, and that b is not available to foo() for the same reason.

ES6 lets you declare variables to belong to individual blocks (pairs of { .. }), using the let keyword

Immediately Invoked Function Expressions (IIFEs)

1
2
3
4
(function IIFE(){
console.log( "Hello!" );
})();
// "Hello!"

The outer ( .. ) that surrounds the (function IIFE(){ .. }) function expression is just a nuance of JS grammar needed to prevent it from being treated as a normal function declaration.

The final () on the end of the expression – the })(); line – is what actually executes the function expression referenced immediately before it.

Because an IIFE is just a function, and functions create variable scope, using an IIFE in this fashion is often used to declare variables that won’t affect the surrounding code outside the IIFE:

1
2
3
4
5
6
7
8
var a = 42;

(function IIFE(){
var a = 10;
console.log( a ); // 10
})();

console.log( a ); // 42

IIFEs can also have return values:

1
2
3
4
5
var x = (function IIFE(){
return 42;
})();

x; // 42

Closure

You can think of closure as a way to “remember” and continue to access a function’s scope (its variables) even once the function has finished running.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function makeAdder(x) {
// parameter `x` is an inner variable

// inner function `add()` uses `x`, so
// it has a "closure" over it
function add(y) {
return y + x;
};

return add;
}

// `plusOne` gets a reference to the inner `add(..)`
// function with closure over the `x` parameter of
// the outer `makeAdder(..)`
var plusOne = makeAdder( 1 );

// `plusTen` gets a reference to the inner `add(..)`
// function with closure over the `x` parameter of
// the outer `makeAdder(..)`
var plusTen = makeAdder( 10 );

plusOne( 3 ); // 4 <-- 1 + 3
plusOne( 41 ); // 42 <-- 1 + 41

plusTen( 13 ); // 23 <-- 10 + 13

More on how this code works:

  1. When we call makeAdder(1), we get back a reference to its inner add(..) that remembers x as 1. We call this function reference plusOne(..).
  2. When we call makeAdder(10), we get back another reference to its inner add(..) that remembers x as 10. We call this function reference plusTen(..).
  3. When we call plusOne(3), it adds 3 (its inner y) to the 1 (remembered by x), and we get 4 as the result.
  4. When we call plusTen(13), it adds 13 (its inner y) to the 10 (remembered by x), and we get 23 as the result.

this Identifier

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function foo() {
console.log( this.bar );
}

var bar = "global";

var obj1 = {
bar: "obj1",
foo: foo
};

var obj2 = {
bar: "obj2"
};

// --------

foo(); // "global"
obj1.foo(); // "obj1"
foo.call( obj2 ); // "obj2"
new foo(); // undefined

There are four rules for how this gets set, and they’re shown in those last four lines of that snippet.

  1. foo() ends up setting this to the global object in non-strict mode – in strict mode, this would be undefined and you’d get an error in accessing the bar property – so "global" is the value found for this.bar.
  2. obj1.foo() sets this to the obj1 object.
  3. foo.call(obj2) sets this to the obj2 object.
  4. new foo() sets this to a brand new empty object.

Prototypes

When you reference a property on an object, if that property doesn’t exist, JavaScript will automatically use that object’s internal prototype reference to find another object to look for the property on. You could think of this almost as a fallback if the property is missing.

The internal prototype reference linkage from one object to its fallback happens at the time the object is created. The simplest way to illustrate it is with a built-in utility called Object.create(..).

1
2
3
4
5
6
7
8
9
10
11
var foo = {
a: 42
};

// create `bar` and link it to `foo`
var bar = Object.create( foo );

bar.b = "hello world";

bar.b; // "hello world"
bar.a; // 42 <-- delegated to `foo`

The a property doesn’t actually exist on the bar object, but because bar is prototype-linked to foo, JavaScript automatically falls back to looking for a on the foo object, where it’s found.

Polyfilling

ES6 defines a utility called Number.isNaN(..) to provide an accurate non-buggy check for NaN values

Transpiling

Here’s a quick example of transpiling. ES6 adds a feature called “default parameter values.”

1
2
3
4
5
6
function foo(a = 2) {
console.log( a );
}

foo(); // 2
foo( 42 ); // 42